package pers.luofei.http.client.core;

import pers.luofei.http.client.annotations.*;
import pers.luofei.http.client.converter.ResponseBodyDecoder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by luofei on 2017/8/31.
 */
public final class RequestMetaInfoCache {

    private static Map<String, RequestMetaInfo> metaInfoCache;

    public static String REQUEST_ADDRESS;

    private static Map<Class<? extends ResponseCallback>, ResponseCallback> callbackCache;

    private static Map<Class<? extends ResponseBodyDecoder>, ResponseBodyDecoder> decoderCache;

    private static Map<Class<? extends RequestAddressDiscovery>, RequestAddressDiscovery> hostDiscoveryCache;

    static {
        REQUEST_ADDRESS = "";
        metaInfoCache = new HashMap<>();
        callbackCache = new HashMap<>();
        decoderCache = new HashMap<>();
        hostDiscoveryCache = new HashMap<>();
    }

    static RequestMetaInfo get(Method method) throws InstantiationException, IllegalAccessException {

        if (method == null) {
            return null;
        }
        RequestMetaInfo metaInfo = metaInfoCache.get(method);
        if (metaInfo == null) {
            metaInfo = parse(method);
            metaInfoCache.put(genRequestId(method), metaInfo);
        }

        return metaInfo;
    }

    private static String genRequestId(Method method) {

        return String.valueOf(method.hashCode() ^ Arrays.hashCode(method.getParameterTypes()));
    }

    private static RequestMetaInfo parse(Method method) {

        Class<?> clazz = method.getDeclaringClass();
        RequestMetaInfo metaInfo = new RequestMetaInfo();
        for (Annotation annotation : clazz.getAnnotations()) {
            if (annotation.annotationType() == RequestProtocol.class) {
                metaInfo.setProtocol(((RequestProtocol) annotation).value());
            } else if (annotation.annotationType() == RequestMapping.class) {
                metaInfo.addUriSuffix(((RequestMapping) annotation).value());
            } else if (annotation.annotationType() == RequestAddress.class) {
                Class<? extends RequestAddressDiscovery> discoveryClass = ((RequestAddress) annotation).addressDiscovery();
                if (discoveryClass == NullRequestAddressDiscovery.class) {
                    metaInfo.setAddress(((RequestAddress) annotation).value());
                } else {
                    RequestAddressDiscovery hostDiscovery = hostDiscoveryCache.get(discoveryClass);
                    if (hostDiscovery == null) {
                        try {
                            hostDiscovery = discoveryClass.newInstance();
                            String balanceId = ((RequestAddress) annotation).balanceId();
                            metaInfo.setAddress(hostDiscovery.getRequestAddress(balanceId));
                        } catch (Exception e) {
                            System.err.println("Cannot initialize custom addressDiscovery.");
                            e.printStackTrace();
                        }
                    }
                }
            } else if (annotation.annotationType() == RequestHeaders.class) {
                String[] tmp = ((RequestHeaders) annotation).headers();
                for (String header : tmp) {
                    if (header == null) {
                        continue;
                    }
                    String[] kv = header.split(":", 2);
                    String v = kv.length > 1 ? kv[1] : "";
                    metaInfo.addDefaultHeader(kv[0], v);
                }
                String header = ((RequestHeaders) annotation).value();
                String[] kv = header.split(":", 2);
                String v = kv.length > 1 ? kv[1] : "";
                metaInfo.addDefaultHeader(kv[0], v);
            }
        }

        metaInfo.setReturnType(method.getGenericReturnType());
        for (Annotation annotation : method.getAnnotations()) {
            if (annotation.annotationType() == RequestMapping.class) {
                metaInfo.addLastUri(((RequestMapping) annotation).value());
                metaInfo.setMethod(((RequestMapping) annotation).method());
            } else if (annotation.annotationType() == GET.class) {
                metaInfo.addLastUri(((GET) annotation).value());
                metaInfo.setMethod(GET.NAME);
            } else if (annotation.annotationType() == POST.class) {
                metaInfo.addLastUri(((POST) annotation).value());
                metaInfo.setMethod(POST.NAME);
            } else if (annotation.annotationType() == PUT.class) {
                metaInfo.addLastUri(((PUT) annotation).value());
                metaInfo.setMethod(PUT.NAME);
            } else if (annotation.annotationType() == DELETE.class) {
                metaInfo.addLastUri(((DELETE) annotation).value());
                metaInfo.setMethod(DELETE.NAME);
            } else if (annotation.annotationType() == RequestContentType.class) {
                metaInfo.setContentType(((RequestContentType) annotation).value());
            } else if (annotation.annotationType() == ResponseContentType.class) {
                metaInfo.setResponseContentType(((ResponseContentType) annotation).value());
                metaInfo.setDecodeNode(((ResponseContentType) annotation).node());
                Class<? extends ResponseCallback> callbackClass = ((ResponseContentType) annotation).callback();
                ResponseCallback callback = callbackCache.get(callbackClass);
                if (callback == null) {
                    try {
                        callback = callbackClass.newInstance();
                        callbackCache.put(callbackClass, callback);
                        metaInfo.setCallback(callback);
                    } catch (Exception e) {
                        System.err.println("Cannot initialize custom callback, use DefaultCallback instead.");
                        e.printStackTrace();
                    }
                }
                metaInfo.setCondition(((ResponseContentType) annotation).condition());
            } else if (annotation.annotationType() == RequestId.class) {
                metaInfo.setId(((RequestId) annotation).value());
            } else if (annotation.annotationType() == ResponseDecoder.class) {
                Class<? extends ResponseBodyDecoder> decoderClass = ((ResponseDecoder) annotation).decoder();
                ResponseBodyDecoder decoder = decoderCache.get(decoderClass);
                if (decoder == null) {
                    try {
                        decoder = decoderClass.newInstance();
                        decoderCache.put(decoderClass, decoder);
                    } catch (Exception e) {
                        System.err.println("Cannot initialize custom decoder, use DefaultBodyDecoder instead.");
                        e.printStackTrace();
                    }
                }
                metaInfo.setDecoder(decoder);
            }
        }
        if (metaInfo.getId() == null) {
            metaInfo.setId(genRequestId(method));
        }
        return metaInfo;
    }
}
